package fr.obsmip.sedoo.client.ui.freetext;

import static com.google.gwt.query.client.GQuery.$;
import static fr.obsmip.sedoo.client.ui.freetext.FreeTextChosen.Chosen;
import static fr.obsmip.sedoo.client.ui.freetext.FreeTextChosen.FREE_TEXT_CHOSEN_DATA_KEY;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.OptionElement;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.event.dom.client.DomEvent.Type;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.i18n.client.HasDirection.Direction;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.HandlerRegistration;
import com.google.web.bindery.event.shared.SimpleEventBus;
import com.watopi.chosen.client.ChosenOptions;
import com.watopi.chosen.client.event.ChosenChangeEvent;
import com.watopi.chosen.client.event.ChosenChangeEvent.ChosenChangeHandler;
import com.watopi.chosen.client.event.UpdatedEvent;

import fr.obsmip.sedoo.client.ui.freetext.event.HidingDropDownEvent;
import fr.obsmip.sedoo.client.ui.freetext.event.HidingDropDownEvent.HidingDropDownHandler;
import fr.obsmip.sedoo.client.ui.freetext.event.MaxSelectedEvent;
import fr.obsmip.sedoo.client.ui.freetext.event.MaxSelectedEvent.MaxSelectedHandler;
import fr.obsmip.sedoo.client.ui.freetext.event.NewValueEvent;
import fr.obsmip.sedoo.client.ui.freetext.event.NewValueEvent.NewValueHandler;
import fr.obsmip.sedoo.client.ui.freetext.event.ReadyEvent;
import fr.obsmip.sedoo.client.ui.freetext.event.ReadyEvent.ReadyHandler;
import fr.obsmip.sedoo.client.ui.freetext.event.ShowingDropDownEvent;
import fr.obsmip.sedoo.client.ui.freetext.event.ShowingDropDownEvent.ShowingDropDownHandler;

public class MultipleValueTextBox extends ListBox implements NewValueHandler {

	/**
	 * Indicates of the ChosenListBox is supported by the current browser. If
	 * not (IE6/7), we fall back on normal select element.
	 * 
	 * @return
	 */
	public static boolean isSupported() {
		return com.watopi.chosen.client.Chosen.isSupported();
	}

	/**
	 * Creates a ChosenListBox widget that wraps an existing &lt;select&gt;
	 * element.
	 * 
	 * This element must already be attached to the document. If the element is
	 * removed from the document, you must call
	 * {@link RootPanel#detachNow(Widget)}.
	 * 
	 * @param element
	 *            the element to be wrapped
	 * @return list box
	 */
	public static MultipleValueTextBox wrap(Element element) {
		assert Document.get().getBody().isOrHasChild(element);

		MultipleValueTextBox listBox = new MultipleValueTextBox(element);

		listBox.onAttach();
		RootPanel.detachOnWindowClose(listBox);

		return listBox;
	}

	private EventBus chznHandlerManager;

	private ChosenOptions options;

	private boolean visible = true;

	/**
	 * Creates an empty chosen component in single selection mode.
	 */
	public MultipleValueTextBox() {
		this(true);
	}

	/**
	 * Creates an empty chosen component in single selection mode.
	 */
	public MultipleValueTextBox(ChosenOptions options) {
		this(true,options);
	}


	/**
	 * Creates an empty list box. The preferred way to enable multiple
	 * selections is to use this constructor rather than
	 * {@link #setMultipleSelect(boolean)}.
	 * 
	 * @param isMultipleSelect
	 *            specifies if multiple selection is enabled
	 */
	public MultipleValueTextBox(boolean isMultipleSelect) {
		this(isMultipleSelect, new ChosenOptions());
		setPlaceholderText("");
	}

	/**
	 * Creates an empty list box. The preferred way to enable multiple
	 * selections is to use this constructor rather than
	 * {@link #setMultipleSelect(boolean)}.
	 * 
	 * @param isMultipleSelect
	 *            specifies if multiple selection is enabled
	 */
	public MultipleValueTextBox(boolean isMultipleSelect, ChosenOptions options) {
		super(Document.get().createSelectElement(isMultipleSelect));
		this.options = options;
		ensureChosenHandlers().addHandler(NewValueEvent.TYPE, this);
	}

	protected MultipleValueTextBox(Element element) {
		super(element);
	}

	/**
	 * Deprecated, use {@link #addChosenChangeHandler(ChosenChangeHandler)}
	 * instead
	 */
	@Override
	@Deprecated
	public com.google.gwt.event.shared.HandlerRegistration addChangeHandler(
			final com.google.gwt.event.dom.client.ChangeHandler handler) {
		final HandlerRegistration registration = addChosenChangeHandler(new ChosenChangeHandler() {
			public void onChange(ChosenChangeEvent event) {
				handler.onChange(null);
			}
		});

		return new com.google.gwt.event.shared.HandlerRegistration() {
			public void removeHandler() {
				registration.removeHandler();
			}
		};
	}

	public HandlerRegistration addChosenChangeHandler(
			ChosenChangeHandler handler) {
		return ensureChosenHandlers().addHandler(ChosenChangeEvent.getType(),
				handler);
	}

	protected final <H extends EventHandler> HandlerRegistration addChosenHandler(
			H handler, Type<H> type) {
		return ensureChosenHandlers().addHandler(type, handler);
	}

	/**
	 * Adds a group at the end of the list box.
	 * 
	 * @param group
	 *            the text of the group to be added
	 */
	public void addGroup(String group) {
		insertGroup(group, -1);
	}

	public HandlerRegistration addHidingDropDownHandler(
			HidingDropDownHandler handler) {
		return ensureChosenHandlers().addHandler(HidingDropDownEvent.getType(),
				handler);
	}

	/**
	 * Adds an item to the last optgroup of the list box.
	 * 
	 * @param item
	 *            the text of the item to be added
	 */
	public void addItemToGroup(String item) {
		insertItemToGroup(item, -1, -1);
	}

	/**
	 * Adds an item to the an optgroup of the list box.
	 * 
	 * @param item
	 *            the text of the item to be added
	 * @param groupIndex
	 *            the index of the optGroup where the item will be inserted
	 */
	public void addItemToGroup(String item, int groupIndex) {
		insertItemToGroup(item, groupIndex, -1);
	}

	/**
	 * Adds an item to the last optgroup of the list box.
	 * 
	 * @param item
	 *            the text of the item to be added
	 */
	public void addItemToGroup(String item, String value) {
		insertItemToGroup(item, value, -1, -1);
	}

	/**
	 * Adds an item to the an optgroup of the list box.
	 * 
	 * @param item
	 *            the text of the item to be added
	 * @param groupIndex
	 *            the index of the optGroup where the item will be inserted
	 */
	public void addItemToGroup(String item, String value, int groupIndex) {
		insertItemToGroup(item, value, groupIndex, -1);
	}

	public HandlerRegistration addMaxSelectedHandler(MaxSelectedHandler handler) {
		return ensureChosenHandlers().addHandler(MaxSelectedEvent.getType(),
				handler);
	}

	public HandlerRegistration addReadyHandler(ReadyHandler handler) {
		return ensureChosenHandlers().addHandler(ReadyEvent.getType(), handler);
	}

	public HandlerRegistration addShowingDropDownHandler(
			ShowingDropDownHandler handler) {
		return ensureChosenHandlers().addHandler(
				ShowingDropDownEvent.getType(), handler);
	}

	protected EventBus ensureChosenHandlers() {
		return chznHandlerManager == null ? chznHandlerManager = new SimpleEventBus()
		: chznHandlerManager;
	}

	public void forceRedraw() {
		$(getElement()).as(Chosen).destroy()
		.freeTextChosen(options, ensureChosenHandlers());
	}

	protected GQuery getChosenElement() {
		FreeTextChosenImpl impl = $(getElement()).data(FREE_TEXT_CHOSEN_DATA_KEY,
				FreeTextChosenImpl.class);
		if (impl != null) {
			return impl.getContainer();
		}
		return $();
	}

	protected EventBus getChosenHandlerManager() {
		return chznHandlerManager;
	}

	public int getDisableSearchThreshold() {
		return options.getDisableSearchThreshold();
	}

	public int getMaxSelectedOptions() {
		return options.getMaxSelectedOptions();
	}

	public String getNoResultsText() {
		return options.getNoResultsText();
	}

	public String getPlaceholderText() {
		return options.getPlaceholderText();
	}

	public String getPlaceholderTextMultiple() {
		return options.getPlaceholderTextMultiple();
	}

	public String getPlaceholderTextSingle() {
		return options.getPlaceholderTextSingle();
	}

	/**
	 * Insert a group to the list box.
	 * 
	 * @param group
	 *            the text of the group to be added
	 * @param index
	 *            the index at which to insert it
	 */
	public void insertGroup(String group, int index) {
		GQuery optGroup = $("<optgroup></optgroup>").attr("label", group);
		GQuery select = $(getElement());

		int itemCount = SelectElement.as(getElement()).getLength();

		if (index < 0 || index > itemCount) {
			select.append(optGroup);
		} else {
			GQuery before = select.children().eq(index);
			before.before(optGroup);
		}
	}

	/**
	 * Adds an item to the an optgroup of the list box. If no optgroup exists,
	 * the item will be add at the end ot the list box.
	 * 
	 * @param item
	 *            the text of the item to be added
	 * @param value
	 *            the value of the item to be added
	 * @param itemIndex
	 *            the index inside the optgroup at which to insert the item
	 * @param groupIndex
	 *            the index of the optGroup where the item will be inserted
	 */
	public void insertItemToGroup(String item, Direction dir, String value,
			int groupIndex, int itemIndex) {

		GQuery select = $(getElement());
		GQuery optgroupList = select.children("optgroup");

		int groupCount = optgroupList.size();

		if (groupCount == 0) {
			// simply insert the item to the listbox
			insertItem(item, dir, value, itemIndex);
			return;
		}

		if (groupIndex < 0 || groupIndex > groupCount - 1) {
			groupIndex = groupCount - 1;
		}

		GQuery optgroup = optgroupList.eq(groupIndex);

		OptionElement option = Document.get().createOptionElement();
		setOptionText(option, item, dir);
		option.setValue(value);

		int itemCount = optgroup.children().size();

		if (itemIndex < 0 || itemIndex > itemCount - 1) {
			optgroup.append(option);
		} else {
			GQuery before = optgroup.children().eq(itemIndex);
			before.before(option);
		}

	}

	/**
	 * Adds an item to the an optgroup of the list box. If no optgroup exists,
	 * the item will be add at the end ot the list box.
	 * 
	 * @param item
	 *            the text of the item to be added
	 * @param itemIndex
	 *            the index inside the optgroup at which to insert the item
	 * @param groupIndex
	 *            the index of the optGroup where the item will be inserted
	 */

	public void insertItemToGroup(String item, int groupIndex, int itemIndex) {
		insertItemToGroup(item, null, item, groupIndex, itemIndex);

	}

	/**
	 * Adds an item to the an optgroup of the list box. If no optgroup exists,
	 * the item will be add at the end ot the list box.
	 * 
	 * @param item
	 *            the text of the item to be added
	 * @param value
	 *            the value of the item to be added
	 * @param itemIndex
	 *            the index inside the optgroup at which to insert the item
	 * @param groupIndex
	 *            the index of the optGroup where the item will be inserted
	 */
	public void insertItemToGroup(String item, String value, int groupIndex,
			int itemIndex) {
		insertItemToGroup(item, null, item, groupIndex, itemIndex);

	}

	/**
	 * Specify if the deselection is allowed on single selects.
	 * 
	 */
	public boolean isAllowSingleDeselect() {
		return options.isAllowSingleDeselect();
	}

	public boolean isSearchContains() {
		return options.isSearchContains();
	}

	public boolean isSingleBackstrokeDelete() {
		return options.isSingleBackstrokeDelete();
	}

	@Override
	protected void onLoad() {
		super.onLoad();
		$(getElement()).as(Chosen).freeTextChosen(options, ensureChosenHandlers());
		setVisible(visible);
	}

	@Override
	protected void onUnload() {
		super.onUnload();
		$(getElement()).as(Chosen).destroy();
	}

	public void setAllowSingleDeselect(boolean allowSingleDeselect) {
		options.setAllowSingleDeselect(allowSingleDeselect);
	}

	public void setDisableSearchThreshold(int disableSearchThreshold) {
		options.setDisableSearchThreshold(disableSearchThreshold);
	}

	public void setMaxSelectedOptions(int maxSelectedOptions) {
		options.setMaxSelectedOptions(maxSelectedOptions);
	}

	public void setNoResultsText(String noResultsText) {
		options.setNoResultsText(noResultsText);
	}

	public void setPlaceholderText(String placeholderText) {
		options.setPlaceholderText(placeholderText);
	}

	public void setPlaceholderTextMultiple(String placeholderTextMultiple) {
		options.setPlaceholderTextMultiple(placeholderTextMultiple);
	}

	public void setPlaceholderTextSingle(String placeholderTextSingle) {
		options.setPlaceholderTextSingle(placeholderTextSingle);
	}

	public void setSearchContains(boolean searchContains) {
		options.setSearchContains(searchContains);
	}

	@Override
	public void setSelectedIndex(int index) {
		super.setSelectedIndex(index);
		update();
	}

	public void setSingleBackstrokeDelete(boolean singleBackstrokeDelete) {
		options.setSingleBackstrokeDelete(singleBackstrokeDelete);
	}

	@Override
	public void setVisible(boolean visible) {
		this.visible = visible;

		if (isSupported()) {
			GQuery chosenElement = getChosenElement();
			if (visible) {
				chosenElement.show();
			} else {
				chosenElement.hide();
			}
		} else {
			super.setVisible(visible);
		}
	}

	/**
	 * Use this method to update the chosen list box (i.e. after insertion or
	 * removal of options)
	 */
	public void update() {
		ensureChosenHandlers().fireEvent(new UpdatedEvent());
	}

	@Override
	public void onNewValue(NewValueEvent event) {

		String value = event.getValue();
		int itemCount = getItemCount();
		boolean found = false;
		for (int i = 0; i < itemCount; i++) 
		{
			if (getItemText(i).compareToIgnoreCase(value)==0)
			{
				if (isItemSelected(i))
				{
					//L'élément existe déjà et est sélectionné : on ne fait rien
					return;
				}
				else
				{
					//L'élément existe déjà mais n'est pas sélectionné : on le supprime, il sera ajouté à la fin de la liste
					removeItem(i);
					break;
				}
			}
		}

		//L'élément n'existe pas, on l'ajoute et on le sélectionne

		addItem(event.getValue());
		setItemSelected(getItemCount()-1, true);
		update();
	}

	public List<String> getValues() 
	{
		List<String> result = new ArrayList<String>();
		int itemCount = getItemCount();
		for (int i = 0; i < itemCount; i++) 
		{
			if (isItemSelected(i))
			{
				String aux = getItemText(i).trim();
				if (aux.length()>0)
				{
					result.add(aux);
				}
			}
		}
		return result;
	}
	
	public void setValues(List<String> values)
	{
		int itemCount = getItemCount();
		for (int i = 0; i < itemCount; i++) 
		{
			removeItem(0);
		}
		if (values != null)
		{
			Iterator<String> iterator = values.iterator();
			while (iterator.hasNext()) 
			{
				addItem(iterator.next());
				setItemSelected(getItemCount()-1, true);	
			}
		}
		update();
	}

	public void reset() {
		setValues(new ArrayList<String>());
		
	}
	
	






}
